export function isNumber(val: unknown): val is number {
    return typeof val === 'number'
}

export function isString(val: unknown): val is string {
    return typeof val ==='string'
}

export function isBoolean(val: unknown): val is boolean {
    return typeof val === 'boolean'
}

export function isArray<T>(val: T): val is T & Array<any> {
    return Array.isArray(val)
}

export function isFunction(val: unknown): val is Function {
    return typeof val === 'function'
}

export function isObject<T>(val: T): val is T & Record<string, any> {
    return Object.prototype.toString.call(val) === '[object Object]'
}

export function isInstanceOf(val: unknown, cls: (new(...args: any) => any)): val is InstanceType<typeof cls> {
    return val instanceof cls
}

/**
 * 判断两个类型是否相等。如果 T1 和 T2 是同一个类型，则返回该类型，否则返回 never。
 */
export type IsEqual<T1, T2, Y = T1, N = never> = (<G>() => G extends T1 ? 1 : 2) extends (<G>() => G extends T2 ? 1 : 2)
                                                 ? Y
                                                 : N

/**
 * 获取一个类型的可变属性联合体。
 */
export type MutableKeys<T> = {
    [K in keyof T]: IsEqual<{ [Q in K]: T[K] }, { -readonly [Q in K]: T[K] }, K>
}[keyof T]

/**
 * 获取一个类型的只读属性联合体。
 */
export type ReadonlyKeys<T> = {
    [K in keyof T]: IsEqual<{ [Q in keyof K]: T[K] }, { -readonly [Q in keyof K]: T[K] }, never, K>
}[keyof T]

/**
 * 返回只包含可变属性的类型副本。
 */
export type RemainMutable<T> = Pick<T, MutableKeys<T>>

/**
 * 返回只包含只读属性的类型副本。
 */
export type RemainReadonly<T> = Pick<T, ReadonlyKeys<T>>

/**
 * 返回过滤掉成员方法的类型副本。
 */
export type FilterFunctions<T> = Pick<T, { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]>


/**
 * JS/TS 的基本类型。
 */
export type Primitive = string | number | boolean | symbol | bigint | null | undefined

/**
 * 简单数组：只能以基本类型、简单数组、简单对象作为元素类型。
 */
export type PlainArray = Array<PlainType>

/**
 * 简单对象：只能以字符串或符号作为键类型，只能以基本类型、简单数组、简单对象作为值类型。
 */
export type PlainObject = {
    [key: string | symbol]: PlainType
}

/**
 * 简单类型：只能是基本类型、简单数组、简单对象。
 */
export type PlainType = Primitive | PlainObject | PlainArray

/**
 * 运行时判断变量类型是否是简单类型。
 */
export function isPrimitive<T>(value: T): value is Extract<T, Primitive> {
    return typeof value === 'string'
        || typeof value === 'number'
        || typeof value === 'boolean'
        || value === null
        || value === undefined
        || typeof value === 'symbol'
        || typeof value === 'bigint'
}

/**
 * 运行时判断变量类型是否是简单数组，会递归检查元素的类型。
 */
export function isPlainArray<T>(value: T): value is Extract<T, PlainArray> {
    if (! Array.isArray(value))
        return false
    
    for (const item of value) {
        if (!isPrimitive(item) && !isPlainObject(item) && !isPlainArray(item))
            return false
    }

    return true
}

/**
 * 运行时判断变量类型是否是简单对象，会递归检查值类型。
 */
export function isPlainObject<T>(value: T): value is Extract<T, PlainObject> {
    if (typeof value !== 'object' || value === null)
        return false

    const proto = Object.getPrototypeOf(value)
    if (proto !== Object.prototype && proto !== null)
        return false

    for (const key in value) {
        if (!isPrimitive((value as PlainObject)[key]) && !isPlainObject((value as PlainObject)[key]) && !isPlainArray((value as PlainObject)[key]))
            return false
    }

    return true
}
